/***********************************************************************************************************************
*  OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
*  See also https://openstudio.net/license
***********************************************************************************************************************/

#include "ConstructionWithInternalSource.hpp"
#include "ConstructionWithInternalSource_Impl.hpp"
#include "Model.hpp"

#include "Material.hpp"
#include "Material_Impl.hpp"
#include "OpaqueMaterial.hpp"
#include "ModelExtensibleGroup.hpp"

#include "../utilities/core/Assert.hpp"
#include <utilities/idd/OS_Construction_InternalSource_FieldEnums.hxx>
#include <utilities/idd/IddEnums.hxx>

namespace openstudio {
namespace model {

  namespace detail {

    ConstructionWithInternalSource_Impl::ConstructionWithInternalSource_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle)
      : LayeredConstruction_Impl(idfObject, model, keepHandle) {
      OS_ASSERT(idfObject.iddObject().type() == ConstructionWithInternalSource::iddObjectType());
    }

    ConstructionWithInternalSource_Impl::ConstructionWithInternalSource_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model,
                                                                             bool keepHandle)
      : LayeredConstruction_Impl(other, model, keepHandle) {
      OS_ASSERT(other.iddObject().type() == ConstructionWithInternalSource::iddObjectType());
    }

    ConstructionWithInternalSource_Impl::ConstructionWithInternalSource_Impl(const ConstructionWithInternalSource_Impl& other, Model_Impl* model,
                                                                             bool keepHandle)
      : LayeredConstruction_Impl(other, model, keepHandle) {}

    const std::vector<std::string>& ConstructionWithInternalSource_Impl::outputVariableNames() const {
      static const std::vector<std::string> result{"Surface Internal Source Location Temperature",
                                                   "Surface Internal User Specified Location Temperature"};
      return result;
    }

    IddObjectType ConstructionWithInternalSource_Impl::iddObjectType() const {
      return ConstructionWithInternalSource::iddObjectType();
    }

    int ConstructionWithInternalSource_Impl::renderingColorIndex() const {
      return OS_Construction_InternalSourceFields::SurfaceRenderingName;
    }

    bool ConstructionWithInternalSource_Impl::eraseLayer(unsigned layerIndex) {
      if (this->numLayers() < 1) {
        return false;
      }
      bool result = LayeredConstruction_Impl::eraseLayer(layerIndex);
      if (result) {
        onNumLayersChanged();
      }
      return result;
    }

    bool ConstructionWithInternalSource_Impl::setLayers(const std::vector<Material>& materials) {
      if (materials.size() < 2) {
        return false;
      }
      bool result = LayeredConstruction_Impl::setLayers(materials);
      if (result) {
        onNumLayersChanged();
      }
      return result;
    }

    bool ConstructionWithInternalSource_Impl::setLayer(const ModelPartitionMaterial& /*modelPartitionMaterial*/) {
      return false;
    }

    int ConstructionWithInternalSource_Impl::sourcePresentAfterLayerNumber() const {
      boost::optional<int> value = getInt(OS_Construction_InternalSourceFields::SourcePresentAfterLayerNumber, true);
      OS_ASSERT(value);
      return value.get();
    }

    bool ConstructionWithInternalSource_Impl::setSourcePresentAfterLayerNumber(int sourcePresentAfterLayerNumber) {
      if (sourcePresentAfterLayerNumber < 1 || sourcePresentAfterLayerNumber > (int)this->numLayers()) {
        return false;
      }
      bool result = setInt(OS_Construction_InternalSourceFields::SourcePresentAfterLayerNumber, sourcePresentAfterLayerNumber);
      return result;
    }

    int ConstructionWithInternalSource_Impl::temperatureCalculationRequestedAfterLayerNumber() const {
      boost::optional<int> value = getInt(OS_Construction_InternalSourceFields::TemperatureCalculationRequestedAfterLayerNumber, true);
      OS_ASSERT(value);
      return value.get();
    }

    bool
      ConstructionWithInternalSource_Impl::setTemperatureCalculationRequestedAfterLayerNumber(int temperatureCalculationRequestedAfterLayerNumber) {
      if (temperatureCalculationRequestedAfterLayerNumber < 1 || temperatureCalculationRequestedAfterLayerNumber > (int)this->numLayers()) {
        return false;
      }
      bool result = setInt(OS_Construction_InternalSourceFields::TemperatureCalculationRequestedAfterLayerNumber,
                           temperatureCalculationRequestedAfterLayerNumber);
      return result;
    }

    int ConstructionWithInternalSource_Impl::dimensionsForTheCTFCalculation() const {
      boost::optional<int> value = getInt(OS_Construction_InternalSourceFields::DimensionsfortheCTFCalculation, true);
      OS_ASSERT(value);
      return value.get();
    }

    bool ConstructionWithInternalSource_Impl::setDimensionsForTheCTFCalculation(int dimensionsForTheCTFCalculation) {
      if (dimensionsForTheCTFCalculation < 1 || dimensionsForTheCTFCalculation > 2) {
        return false;
      }
      bool result = setInt(OS_Construction_InternalSourceFields::DimensionsfortheCTFCalculation, dimensionsForTheCTFCalculation);
      return result;
    }

    double ConstructionWithInternalSource_Impl::tubeSpacing() const {
      boost::optional<double> value = getDouble(OS_Construction_InternalSourceFields::TubeSpacing, true);
      OS_ASSERT(value);
      return value.get();
    }

    bool ConstructionWithInternalSource_Impl::setTubeSpacing(double tubeSpacing) {
      bool result = setDouble(OS_Construction_InternalSourceFields::TubeSpacing, tubeSpacing);
      return result;
    }

    double ConstructionWithInternalSource_Impl::twoDimensionalTemperatureCalculationPosition() const {
      boost::optional<double> value = getDouble(OS_Construction_InternalSourceFields::TwoDimensionalTemperatureCalculationPosition, true);
      OS_ASSERT(value);
      return value.get();
    }

    bool ConstructionWithInternalSource_Impl::setTwoDimensionalTemperatureCalculationPosition(double twoDimensionalTemperatureCalculationPosition) {
      bool result =
        setDouble(OS_Construction_InternalSourceFields::TwoDimensionalTemperatureCalculationPosition, twoDimensionalTemperatureCalculationPosition);
      return result;
    }

    ConstructionWithInternalSource ConstructionWithInternalSource_Impl::reverseConstructionWithInternalSource() const {
      MaterialVector reverseLayers(this->layers());
      std::reverse(reverseLayers.begin(), reverseLayers.end());

      int numLayers = (int)this->numLayers();
      int reverseSourcePresentAfterLayerNumber = numLayers - this->sourcePresentAfterLayerNumber();
      int reverseTemperatureCalculationRequestedAfterLayerNumber = numLayers - this->temperatureCalculationRequestedAfterLayerNumber();
      int dimensionsForTheCTFCalculation = this->dimensionsForTheCTFCalculation();
      double tubeSpacing = this->tubeSpacing();
      double twoDimensionalTemperatureCalculationPosition = this->twoDimensionalTemperatureCalculationPosition();

      Model model = this->model();
      for (const ConstructionWithInternalSource& other : model.getConcreteModelObjects<ConstructionWithInternalSource>()) {

        if (other.sourcePresentAfterLayerNumber() != reverseSourcePresentAfterLayerNumber) {
          continue;
        }

        if (other.temperatureCalculationRequestedAfterLayerNumber() != reverseTemperatureCalculationRequestedAfterLayerNumber) {
          continue;
        }

        if (other.dimensionsForTheCTFCalculation() != dimensionsForTheCTFCalculation) {
          continue;
        }

        if (other.tubeSpacing() != tubeSpacing) {
          continue;
        }

        if (other.twoDimensionalTemperatureCalculationPosition() != twoDimensionalTemperatureCalculationPosition) {
          continue;
        }

        MaterialVector layers = other.layers();
        if (layers.size() != reverseLayers.size()) {
          continue;
        }

        bool test = true;
        for (unsigned i = 0; i < layers.size(); ++i) {
          if (layers[i].handle() != reverseLayers[i].handle()) {
            test = false;
            break;  // break out of loop over layers
          }
        }

        if (test) {
          return other;
        }
      }

      // TODO: this should also copy (and modify) standards information object

      // no match, make one
      ConstructionWithInternalSource result(model);
      result.setName(this->name().get() + " Reversed");
      result.setSourcePresentAfterLayerNumber(reverseSourcePresentAfterLayerNumber);
      result.setTemperatureCalculationRequestedAfterLayerNumber(reverseTemperatureCalculationRequestedAfterLayerNumber);
      result.setDimensionsForTheCTFCalculation(dimensionsForTheCTFCalculation);
      result.setTubeSpacing(tubeSpacing);
      result.setTwoDimensionalTemperatureCalculationPosition(twoDimensionalTemperatureCalculationPosition);
      result.setLayers(reverseLayers);

      return result;
    }

    void ConstructionWithInternalSource_Impl::onNumLayersChanged() {
      int numLayers = (int)this->numLayers();
      if (this->sourcePresentAfterLayerNumber() > numLayers - 1) {
        bool test = setString(OS_Construction_InternalSourceFields::SourcePresentAfterLayerNumber, "");
        OS_ASSERT(test);
      }
      if (this->temperatureCalculationRequestedAfterLayerNumber() > numLayers - 1) {
        bool test = setString(OS_Construction_InternalSourceFields::TemperatureCalculationRequestedAfterLayerNumber, "");
        OS_ASSERT(test);
      }
    }

  }  // namespace detail

  ConstructionWithInternalSource::ConstructionWithInternalSource(const Model& model)
    : LayeredConstruction(ConstructionWithInternalSource::iddObjectType(), model) {
    OS_ASSERT(getImpl<detail::ConstructionWithInternalSource_Impl>());
  }

  ConstructionWithInternalSource::ConstructionWithInternalSource(const std::vector<OpaqueMaterial>& opaqueMaterials)
    : LayeredConstruction(ConstructionWithInternalSource::iddObjectType(),
                          (opaqueMaterials.empty() ? openstudio::model::Model() : opaqueMaterials.at(0).model())) {
    if (opaqueMaterials.empty()) {
      // DLM: do not remove, this was only added to a temporary model
      //this->remove();
      LOG_AND_THROW("Cannot create an internal source construction with empty layers");
    } else if (opaqueMaterials.size() < 2) {
      this->remove();
      LOG_AND_THROW("Cannot create an internal source construction with fewer than 2 layers");
    } else if (opaqueMaterials.size() > 10) {
      this->remove();
      LOG_AND_THROW("Cannot create an internal source construction with more than 10 layers");
    }

    std::vector<Material> materials = castVector<Material>(opaqueMaterials);
    bool ok = setLayers(materials);
    OS_ASSERT(ok);
  }

  IddObjectType ConstructionWithInternalSource::iddObjectType() {
    IddObjectType result(IddObjectType::OS_Construction_InternalSource);
    return result;
  }

  int ConstructionWithInternalSource::sourcePresentAfterLayerNumber() const {
    return getImpl<detail::ConstructionWithInternalSource_Impl>()->sourcePresentAfterLayerNumber();
  }

  bool ConstructionWithInternalSource::setSourcePresentAfterLayerNumber(int sourcePresentAfterLayerNumber) {
    return getImpl<detail::ConstructionWithInternalSource_Impl>()->setSourcePresentAfterLayerNumber(sourcePresentAfterLayerNumber);
  }

  int ConstructionWithInternalSource::temperatureCalculationRequestedAfterLayerNumber() const {
    return getImpl<detail::ConstructionWithInternalSource_Impl>()->temperatureCalculationRequestedAfterLayerNumber();
  }

  bool ConstructionWithInternalSource::setTemperatureCalculationRequestedAfterLayerNumber(int temperatureCalculationRequestedAfterLayerNumber) {
    return getImpl<detail::ConstructionWithInternalSource_Impl>()->setTemperatureCalculationRequestedAfterLayerNumber(
      temperatureCalculationRequestedAfterLayerNumber);
  }

  int ConstructionWithInternalSource::dimensionsForTheCTFCalculation() const {
    return getImpl<detail::ConstructionWithInternalSource_Impl>()->dimensionsForTheCTFCalculation();
  }

  bool ConstructionWithInternalSource::setDimensionsForTheCTFCalculation(int dimensionsForTheCTFCalculation) {
    return getImpl<detail::ConstructionWithInternalSource_Impl>()->setDimensionsForTheCTFCalculation(dimensionsForTheCTFCalculation);
  }

  double ConstructionWithInternalSource::tubeSpacing() const {
    return getImpl<detail::ConstructionWithInternalSource_Impl>()->tubeSpacing();
  }

  bool ConstructionWithInternalSource::setTubeSpacing(double tubeSpacing) {
    return getImpl<detail::ConstructionWithInternalSource_Impl>()->setTubeSpacing(tubeSpacing);
  }

  double ConstructionWithInternalSource::twoDimensionalTemperatureCalculationPosition() const {
    return getImpl<detail::ConstructionWithInternalSource_Impl>()->twoDimensionalTemperatureCalculationPosition();
  }

  bool ConstructionWithInternalSource::setTwoDimensionalTemperatureCalculationPosition(double twoDimensionalTemperatureCalculationPosition) {
    return getImpl<detail::ConstructionWithInternalSource_Impl>()->setTwoDimensionalTemperatureCalculationPosition(
      twoDimensionalTemperatureCalculationPosition);
  }

  ConstructionWithInternalSource ConstructionWithInternalSource::reverseConstructionWithInternalSource() const {
    return getImpl<detail::ConstructionWithInternalSource_Impl>()->reverseConstructionWithInternalSource();
  }

  /// @cond
  ConstructionWithInternalSource::ConstructionWithInternalSource(std::shared_ptr<detail::ConstructionWithInternalSource_Impl> impl)
    : LayeredConstruction(std::move(impl)) {}
  /// @endcond

}  // namespace model
}  // namespace openstudio
